<ui:component xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:tr="http://myfaces.apache.org/trinidad"
    xmlns:trh="http://myfaces.apache.org/trinidad/html" xmlns:c="http://java.sun.com/jstl/core"
    xmlns:ajsfc="http://www.andromda.org/cartridges/jsf/facelets">
    <c:set var="jsIdPrefix" value="" />
    <c:set var="jsVarPrefix" value="" />
    <c:if test="#{not empty parentId}">
        <c:set var="jsIdPrefix" value="#{parentId}:" />
        <c:set var="jsVarPrefix" value="#{parentId}_" />
    </c:if>
    <c:set var="autocompleteSubformId" value="_#{id}_subformAutocomplete" />
    <c:set var="componentsIdPrefix" value="#{jsIdPrefix}#{autocompleteSubformId}" />
    <c:set var="jsEditId" value="#{jsIdPrefix}#{autocompleteSubformId}:edit" />
    <c:set var="jsTableId" value="#{componentsIdPrefix}:table" />
    <c:set var="autocompleteVarName" value="_#{jsVarPrefix}__#{id}_Autocomplete" />
    <c:set var="flagIsPPR" value="_#{autocompleteVarName}_flagIsPPR" />
    <c:set var="onloadFunc" value="_#{autocompleteVarName}_onloadFunc" />
    <c:set var="jsInputHiddenId" value="#{jsIdPrefix}#{id}" />
    <!-- The inputText is duplicated to allow the valueChangeListener... I could not find a better way -->
    <tr:inputText id="#{id}" value="#{value}" required="#{required}" simple="true"
        contentStyle="display:none" autoSubmit="#{autoSubmit}" immediate="#{immediate}"
        valueChangeListener="#{valueChangeListenerBean[valueChangeListenerName]}"
        rendered="#{(not empty valueChangeListenerBean) or (not empty valueChangeListenerName)}" />
    <tr:inputText id="#{id}" value="#{value}" required="#{required}" simple="true"
        contentStyle="display:none" autoSubmit="#{autoSubmit}" immediate="#{immediate}"
        rendered="#{(empty valueChangeListenerBean) and (empty valueChangeListenerName)}" />
    <trh:script
        rendered="#{empty __autocompleteClassAlreadyRendered__ || !__autocompleteCommonFunctionsAlreadyRendered__}">
        function __JSFAutoComplete__( orgValue, orgEditValue, inputHiddenId, componentsIdPrefix, onchange, autocompleteVarName) {
            this.componentsIdPrefix=componentsIdPrefix;
            this.timeout=null;
            this.lineArray=null;
            this.selectedLine=null;
            this.onchange=onchange;
            this.oldOnloadFunc=window.onload;
            this.edit=document.getElementById(this.componentsIdPrefix+':edit');
            this.action=document.getElementById(this.componentsIdPrefix+':action');
            this.hidden=document.getElementById(inputHiddenId);
            this.autocompleteVarName = autocompleteVarName;
            this.tableId=this.componentsIdPrefix+':table'; //since the table is rendered (or not) dinamically, we keep the id instead of the instance.
            if(orgValue == null &amp;&amp; orgEditValue == null){ //keep the current components values
                this.orgValue=this.hidden.value;
                this.orgEditValue=this.edit.value;
                this.hiddenEditValue=this.edit.value;
            } else { //force the new values
                this.orgValue=orgValue;
                this.orgEditValue=orgEditValue;
                this.hiddenEditValue=orgEditValue;
                
                this.hidden.value=this.orgValue;
                this.edit.value=this.orgEditValue;
            }
        }
        
        __JSFAutoComplete__.prototype.showResultTable= function( ignoreEdit ){
            window.clearTimeout(this.timeout);
            if(ignoreEdit || this.edit.value != ''){
                this.selectedLine = null;
                this.lineArray = null;

                this.action.click();
            } else {
                this.hideResultTable();
            } 
        }
        
        __JSFAutoComplete__.prototype.setValuesFromTable= function(hiddenValue, editValue ){
            this.orgValue = this.hidden.value;
            this.orgEditValue = this.hiddenEditValue;
            this.hiddenEditValue = editValue;
            this.edit.value=editValue;             //the order matters

            this.hidden.value=hiddenValue; 
            this.onchangeFunc();
            
            this.hideResultTable();
        }

        __JSFAutoComplete__.prototype.onchangeFunc= function(){
                if(this.onchange != ''){
                    if( eval('function __JSFAutoComplete__onchange(){ '+this.onchange+' }; window.__JSFAutoComplete__OnChangeValue=__JSFAutoComplete__onchange();') ){
                        if( window.__JSFAutoComplete__OnChangeValue &amp;&amp; this.hidden.onchange){
                            this.hidden.onchange();
                        }
                    }
                } else {
                        if( this.hidden.onchange ){
                            this.hidden.onchange();
                        }
                }
        }
        
        __JSFAutoComplete__.prototype.hideResultTable= function(){
            window.clearTimeout(this.timeout);
            this.timeout=null;
            document.getElementById(this.tableId).style.display='none';
            this.selectedLine=null;
            this.lineArray=null;
        }
        
        __JSFAutoComplete__.prototype.checkEmptyEdit= function(){
            if(this.hidden.value == ''){
                this.edit.value = '';
            } else {
                this.edit.value = this.edit.value.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); //trim 
                if(this.edit.value == ''){
                    this.hidden.value = '';
                }
            }
        }
        
        __JSFAutoComplete__.prototype.editKeydown= function(_event) {
            window.clearTimeout(this.timeout);
            var event = window.event || _event;
        
            var _keyCode = event.keyCode ? event.keyCode : event.which ? event.which : event.charCode;
            //alert(event.shiftKey+'-'+_keyCode);
            if(_keyCode==46 &amp;&amp; event.shiftKey){ //shift+del
                this.setValuesFromTable('', '');                        
                return false;
            } else if(this.selectedLine == null){
                if(_keyCode==40) { //down arrow
                    this.showResultTable(true);
                }else if(_keyCode==27){ //esc
                    this.setValuesFromTable(this.orgValue, this.orgEditValue);                        
                    return false;
                } else {
                    this.timeout=window.setTimeout(this.autocompleteVarName+".showResultTable(false);",900);
                }
            } else {
                if(_keyCode==38){//up arrow
                    if(this.selectedLine &gt; 0){
                        var theSelectedLine=document.getElementById(this.tableId+this.selectedLine).className='autocomplete-line';
                        --this.selectedLine;
                        document.getElementById(this.tableId+this.selectedLine).className='autocomplete-selected-line';
                    }
                }else if(_keyCode==40){//down arrow
                    if((this.lineArray.length - 1) > this.selectedLine){
                        if(this.selectedLine &gt; -1){
                            document.getElementById(this.tableId+this.selectedLine).className='autocomplete-line';
                        }
                        ++this.selectedLine;
                        document.getElementById(this.tableId+this.selectedLine).className='autocomplete-selected-line';
                    }
                }else if(_keyCode==27){ //esc
                    this.setValuesFromTable(this.orgValue, this.orgEditValue);                        
                    return false;
                }else if(_keyCode==13){ //enter
                    document.getElementById(this.tableId+this.selectedLine).onmousedown();
                    return false;
                }else if(_keyCode==9){ //tab
                    document.getElementById(this.tableId+this.selectedLine).onmousedown();
                } else {
                    this.timeout=window.setTimeout(this.autocompleteVarName+".showResultTable(false);",900);
                }
            }
            return true;
        }
        
        __JSFAutoComplete__.prototype.checkChangeEdit= function(){
            if(this.edit.value == ''){
                this.hidden.value = '';
            }else if((this.selectedLine==null) &amp;&amp; (this.hiddenEditValue!=this.edit.value)){
                this.edit.value = this.hiddenEditValue;
                this.hidden.value = this.orgValue;
            }
        }
        
        __JSFAutoComplete__.prototype.resetOrgValues= function(){
            this.orgValue = this.hidden.value;
            this.orgEditValue = this.edit.value;
            this.hiddenEditValue = this.orgEditValue;
        }
        
        __JSFAutoComplete__.prototype.findPosX= function(obj)
        {
            var curleft = 0;
	    var tableOffsetLeft=0;	
            var tableOffsetParent = document.getElementById(this.tableId).offsetParent;
            if(tableOffsetParent)
            tableOffsetLeft=tableOffsetParent.offsetLeft;

            if(obj.offsetParent){
                while(1) 
                {
                  curleft += obj.offsetLeft;
                    curleft -= this.getSideOffset(obj,"Left");
                    
                  if(!obj.offsetParent)
                    break;
                    
                  obj = obj.offsetParent;
                }
            }    
            else if(obj.x){
                curleft += obj.x;
            }
            return curleft-tableOffsetLeft;
        }
        
        __JSFAutoComplete__.prototype.findPosY= function(obj)
        {
            var curtop = 0;
	    var tableOffsetTop=0;	
            var tableOffsetParent = document.getElementById(this.tableId).offsetParent;
            if(tableOffsetParent)
		tableOffsetTop=tableOffsetParent.offsetTop;

            if(obj.offsetParent){
                while(1)
                {
                  
                  curtop += obj.offsetTop;
                  curtop -= this.getSideOffset(obj,"Top");
                    
                  if(!obj.offsetParent)
                    break;
                    
                  obj = obj.offsetParent;
                }
            }    
            else if(obj.y){
                curtop += obj.y;
            }
            return curtop-tableOffsetTop;
        }
        
        __JSFAutoComplete__.prototype.getSideOffset= function(elem, side)
        {
              var arr = [ "border", "padding", "margin" ];
              var val = 0;
              for (var i = 0; i &lt; arr.length; ++i)
              {
                var o = TrUIUtils._getStyle(elem, arr[i] + side);
                o = parseInt(o);
                if (!isNaN(o))
                {
                  val += o;
                }
              }
              return val;
        }
        
        __JSFAutoComplete__.prototype.pprMonitor= function(state)
        {
            var busy = state == TrRequestQueue.STATE_BUSY;
            if(busy){
               this.action.className = 'autocomplete-button-action';
            } else {
                this.action.className = 'autocomplete-button';
                
                var firstElement = document.getElementById(this.tableId+'0');
                if(firstElement){
                   var table=document.getElementById(this.tableId);
                   this.lineArray = table.getElementsByTagName('TD');
                   this.selectedLine = 0;
                   firstElement.className='autocomplete-selected-line';
        
                   table.style.left =this.findPosX(this.edit)+"px";
                   table.style.top = (this.findPosY(this.edit)+eval(this.edit.offsetHeight)+this.getSideOffset(this.edit, "Bottom"))+"px";
                   table.style.width =this.edit.offsetWidth +"px";
                   firstElement.parentNode.style.width =this.edit.offsetWidth +"px";
                   table.style.display="";
                    
                   this.edit.focus();
                } else {
                   this.selectedLine = null;
                   this.lineArray = null;
                }
            }
        }
    </trh:script>
    <!-- it isn't working because the only supported scope the Facelets c:set is page. maybe in JSF 2...-->
    <c:set var="__autocompleteCommonFunctionsAlreadyRendered__" value="true" scope="request" />
    <tr:subform id="#{autocompleteSubformId}">
        <tr:panelHorizontalLayout valign="middle">
            <h:inputText id="edit" autocomplete="off" required="#{required}" style="#{contentStyle}"
                readonly="#{readOnly}" styleClass="autocomplete-input-text"
                onkeydown="return #{autocompleteVarName}.editKeydown(event);"
                onchange="#{autocompleteVarName}.checkChangeEdit(); return true;"
                onblur="window.clearTimeout(#{autocompleteVarName}.timeout); #{autocompleteVarName}.checkEmptyEdit();#{autocompleteVarName}.timeout=window.setTimeout('#{autocompleteVarName}.hideResultTable();',100);#{autocompleteVarName}.resetOrgValues();"
                value="#{value}" converter="#{ajsfc:getConverter(itemConverterId)}">
            </h:inputText>
            <tr:commandButton blocking="true" id="action" styleClass="autocomplete-button"
                actionListener="#{locateBackingBean[locateActionListenerName]}" partialSubmit="true"
                text="" immediate="true"
                onclick="#{autocompleteVarName}_addPPRListener(); return true;">
                <f:param name="searchFieldRequestParamName" value="#{jsEditId}" />
            </tr:commandButton>
        </tr:panelHorizontalLayout>
        <tr:table styleClass="autocomplete-table-list"
            inlineStyle=" #{empty autocompleteResult ? 'display:none': ''}" id="table" var="row"
            value="#{autocompleteResult}" rows="20" width="100%" partialTriggers="::action"
            varStatus="status">
            <tr:column styleClass="autocomplete-table-line">
                <div id="#{jsTableId}#{status.index}" class="autocomplete-line"
                    onmouseover="document.getElementById('#{jsTableId}0').className = 'autocomplete-line'; this.className='autocomplete-selected-line';"
                    onmouseout="this.className = 'autocomplete-line';"
                    onmousedown="#{autocompleteVarName}.setValuesFromTable('#{row[locateValueFieldName]}','#{row[locateVisibleFieldName]}');">
                <c:if test="#{not empty locateVisibleFieldName}">
                    <h:outputText value="#{row[locateVisibleFieldName]}" />
                </c:if> <c:if test="#{empty locateVisibleFieldName}">
                    <h:outputText value="#{row[locateValueFieldName]}"
                        converter="#{itemConverterId}" />
                </c:if></div>
            </tr:column>
        </tr:table>
        <trh:script>
            //when the page in being rendered, the values are set to null to keep the value already rendered in the fields.
            var #{autocompleteVarName} = new __JSFAutoComplete__(null,null,"#{jsInputHiddenId}","#{componentsIdPrefix}","#{onchange}","#{autocompleteVarName}");
            var #{flagIsPPR};
            
            function #{autocompleteVarName}_addPPRListener()
            {
                TrPage.getInstance().getRequestQueue().addStateChangeListener(#{autocompleteVarName}_pprMonitor);
            }
        
            function #{autocompleteVarName}_pprMonitor(state)
            {
                if(state != TrRequestQueue.STATE_BUSY){
                    TrPage.getInstance().getRequestQueue().removeStateChangeListener(#{autocompleteVarName}_pprMonitor);
                }
                #{autocompleteVarName}.pprMonitor(state);
            }
            
        </trh:script>
    </tr:subform>
    <trh:script partialTriggers="#{ajsfc:splitPartialTriggers(partialTriggers)}">
        if(typeof #{flagIsPPR} == 'undefined'){//only occurs if the page was refreshed
            #{flagIsPPR} = true;
        } else { //PPR
            #{autocompleteVarName} = new __JSFAutoComplete__("#{value}","#{(empty itemConverterId) ? value : (empty value) ? '' : ajsfc:valueFromConverter(value,itemConverterId)}","#{jsInputHiddenId}","#{componentsIdPrefix}","#{onchange}","#{autocompleteVarName}");            
            #{autocompleteVarName}.hidden.value='#{value}';
            #{autocompleteVarName}.edit.value='#{(empty itemConverterId) ? value : (empty value) ? '' : ajsfc:valueFromConverter(value,itemConverterId)}';
        }
    </trh:script>
</ui:component>
